﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WodLib
{
    [Serializable()]
    [System.ComponentModel.Editor(typeof(WodLib.Controls.StateOfBeingEditor),
    typeof(System.Drawing.Design.UITypeEditor))]
    public class StateOfBeing : ICloneable
    {
        private List<DamageType> dmgList = new List<DamageType>(14);

        private int totalBoxes = 0;

        /// <summary>
        /// Calculates number of last boxes counted as damage. Used in reducing boxes.
        /// </summary>
        /// <param name="boxesToReduce">Number of boxes last boxes to count damage in.</param>
        /// <returns>Returns count of last boxes marked as damage.</returns>
        private int CalculateDmgInReducedBoxes(int boxesToReduce)
        {
            int counter = 0;
            for (int i = totalBoxes - boxesToReduce; i < totalBoxes; i++)
            {
                if (dmgList[i] != DamageType.None)
                {
                    counter++;
                }
            }
            return counter;
        }

        /// <summary>
        /// Represents total boxes count.
        /// </summary>
        private int TotalBoxes 
        {
            get
            {
                return totalBoxes;
            }
            set
            {
                if (value > totalBoxes) //there are not enough boxes
                {
                    //add extra boxes
                    int difference = value - totalBoxes;
                    for (int i = 0; i < difference; i++)
                    {
                        dmgList.Add(DamageType.None);
                    }
                    totalBoxes = value;
                }
                else if (value < totalBoxes) //boxes should be reduced
                {
                    //upgrade damage for each filled box
                    int boxesToReduce = totalBoxes - value;
                    int countOfDmg = CalculateDmgInReducedBoxes(boxesToReduce);
                    totalBoxes = value;
                    boxesFilled -= countOfDmg;
                    for (int i = 0; i < countOfDmg; i++)
                    {
                        UpgradeDamage();
                    }
                    dmgList.RemoveRange(dmgList.Count - countOfDmg ,countOfDmg);
                }
                //nothing changes
            }
        }

        private int boxesFilled = 0;

        private int maxHealthMod = 0;

        /// <summary>
        /// Represents magical, temporary modifier for max health.
        /// </summary>
        public int MaxHealthMod 
        {
            get
            {
                return maxHealthMod;
            }
            set
            {
                maxHealthMod = value;
                TotalBoxes = baseBoxes + maxHealthMod;
            }
        }

        private void SetDamageCollectively(int from, int to, DamageType dmgType)
        {
            for (int i = from; i <= to; i++)
            {
                dmgList[i] = dmgType;
            }
        }

        private int baseBoxes = 0;

        public void SetBaseMaxHealth(int baseMaxHealth)
        {
            baseBoxes = baseMaxHealth;
            TotalBoxes = baseBoxes + MaxHealthMod;
        }

        public int DiceRollPenalty
        {
            get
            {
                int penalty = 0;
                int i = TotalBoxes - 3;
                if (i < 0)
                    i = 0;
                for ( ; i < TotalBoxes; i++)
                {
                    if (dmgList[i] != DamageType.None)
                    {
                        penalty--;
                    }
                }
                return penalty;
            }
        }

        public StateOfBeing()
        {
        }

        /// <summary>
        /// Chcecks if charracter is alive.
        /// </summary>
        public bool IsAlive 
        {
            get
            {
                if (LastBoxDmg == DamageType.Aggravated)
                    return false;
                else
                    return true;
            }
        }
        
        /// <summary>
        /// Checks if character is dying and his damages should be upgraded every one minute.
        /// </summary>
        public bool IsDying
        {
            get
            {
                if (LastBoxDmg == DamageType.Lethal)
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Checks if incapacitation roll shoud be performed.
        /// </summary>
        public bool DoIncapacitationRoll
        {
            get
            {
                if (LastBoxDmg >= DamageType.Bashing)
                    return true;
                else
                    return false;
            }
        }

        private char DamageTypeToChar(DamageType dmgType)
        {
            switch (dmgType)
            {
                case DamageType.Bashing:
                    return '/';
                case DamageType.Lethal:
                    return 'x';
                case DamageType.Aggravated:
                    return '*';
                case DamageType.None:
                    return '_' ;
                default:
                    return 'E';
            }
        }
            
        private DamageType FirstBoxDmg { get { return dmgList[0]; } }

        private DamageType LastDmg { get { return dmgList[ boxesFilled == 0 ? 0 : boxesFilled -1]; } }

        private DamageType LastBoxDmg 
        { 
            get 
            {
                if (this.TotalBoxes <= 0)
                    return DamageType.Aggravated;
                return dmgList[this.TotalBoxes - 1]; 
            } 
        }

        private void InsertDamage(int index, DamageType dmg)
        {
            dmgList.Insert(index, dmg);
            dmgList.RemoveAt(dmgList.Count - 1);
            if (boxesFilled < totalBoxes)
            {
                boxesFilled++;
            }
        }

        private void InsertDamage(DamageType dmg)
        {
            if (!IsAlive)
                return;
            for (int i = 0; i < TotalBoxes; i++) //go trough all
            {
                if (dmg > dmgList[i])
                {
                    InsertDamage(i, dmg);
                    return;
                }
            }
            InsertDamage(0, dmg);
       }

        private int FindLeastSevereDmgIndex()
        {
            DamageType last = LastDmg;
            for (int i = boxesFilled - 1; i >= 0; i--)
            {
                if (dmgList[i] != last)
                {
                    return i + 1;
                }
            }
            return 0;
        }

        private int FindMostSevereDmgIndex()
        {
            DamageType tmpDmg = FirstBoxDmg;
            int index = 0;
            for (int i = 0; i < boxesFilled; i++)
            {
                if (dmgList[i] >= tmpDmg)
                {
                    index = i;
                }
            }
            return index;
        }

        private void UpgradeDamage()
        {
            if (!IsAlive)
                return;
            dmgList[FindLeastSevereDmgIndex()]++;
        }

        private bool AreAllBoxesFilled()
        {
            System.Diagnostics.Debug.Assert(boxesFilled <= totalBoxes, "Cannot fill more boxes than exist.");
            return this.boxesFilled == this.totalBoxes;
        }

        /// <summary>
        /// Deals one point of specified type of damage.
        /// </summary>
        /// <param name="dmg">Type of dmage to deal.</param>
        private void DealDamage(DamageType dmg)
        {
            if (!this.IsAlive)
                return;

            //can insert and upgrade
            if (AreAllBoxesFilled())
            {
                if (LastDmg < dmg)
                {
                    InsertDamage(dmg);
                }
                else
                {
                    UpgradeDamage();
                }
            }
            else //insert, cannot upgrade
            {
                InsertDamage(dmg);
            }
        }

        /// <summary>
        /// Deals damage of specified type in specified count.
        /// </summary>
        /// <param name="dmg">Type of damage to deal.</param>
        /// <param name="count">Number of damage points to deal.</param>
        public void DealDamage(DamageType dmg, int count)
        {
            for (int i = 0; i < count; i++)
            {
                DealDamage(dmg);
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('|');
            for (int i = 0; i < totalBoxes; i++)
            {
                sb.Append(this.DamageTypeToChar(dmgList[i]));
                sb.Append('|');
            }
            return sb.ToString();
        }

        /// <summary>
        /// Heals last wound.
        /// </summary>
        public void HealDamage()
        {
            if (this.boxesFilled == 0)
                return;
            this.dmgList[this.boxesFilled - 1] = DamageType.None;
            this.boxesFilled--;
        }

        /// <summary>
        /// Downgrades most severe wound.
        /// </summary>
        public void DowngradeWound()
        {
            if (TotalBoxes < 1)
                return;

            int index = FindMostSevereDmgIndex();
            if (dmgList[index] > DamageType.Bashing)
            {
                dmgList[index]--;
            }
        }

        #region ICloneable Members

        public object Clone()
        {
            StateOfBeing res = (StateOfBeing )this.MemberwiseClone();
            res.dmgList = new List<DamageType>(this.dmgList);
            return (object)res;
        }

        #endregion
    }
}
